# _*_ encoding:utf-8 _*_
# @Time : 2021/6/21 16:41
# @Author : gx

import pandas as pd
import numpy as np
import traceback
from statsmodels.tsa.arima_model import ARIMA, ARMA
from datetime import datetime, date
from itertools import product
from app.data.car_flow import fetch_data, save_data, fetch_kkbh_list
from app.utils.logger_helper import logger
from app.utils.config_helper import configger
import warnings
warnings.filterwarnings('ignore')


class PreModel(object):

    def __init__(self):

        self.ps = configger.model_ps
        self.qs = configger.model_qs
        self.rate = configger.model_rate  # 空值数量占整体数据的比率

        self.start_time = configger.start_time
        self.end_time = configger.end_time

    # 获取卡口全量信息 --> list
    # def get_bayonet_info(self):
    #     bayonet_list = pd.read_excel(r'E:\code_model\zhtx-web\app\alg\卡口信息表.xlsx')
    #     bayonet_list = np.hstack(bayonet_list.values)
    #     return bayonet_list
    def get_bayonet_info(self):
        try:
            bayonet_list = list(fetch_kkbh_list())
            if isinstance(bayonet_list, list):
                return bayonet_list
            else:
                logger.error('error：获取排口信息错误')
                return None
        except Exception as ex:
            logger.error(ex)
            logger.error(traceback.format_exc())
            return None

    # 处理arma模型需要的数据，并对数据进行检验
    def model_data_analysis(self, kkid):
        # 提取卡口数据
        data = pd.read_pickle(self.path)[kkid]
        index = pd.date_range(data.index[0], data.index[-1], freq='H')
        new_data = pd.DataFrame(index=index)
        new_data = pd.concat([new_data, data], axis=1)
        # 均值填补空值
        new_data = new_data.fillna(round(new_data.mean(), 0))
        return new_data

    # 选择特定时刻的数据，根据datetime日期，选取数据拆分训练集和测试集
    def model_data_hour(self, data, hour=10, datetime='2021-05-07'):
        data_hour = data[data.index.hour == hour]
        train = data_hour.loc[:datetime][:-1]
        test = data_hour.loc[:datetime][-1:]
        return train, test

    # 模型训练加预测
    def model_aram_pre(self, train):

        # 根据实际数据需要调整p和q 对应的数值，需要适当调小

        ps = range(0, self.ps)
        qs = range(0, self.qs)

        parameters = product(ps, qs)
        parameters_list = list(parameters)

        best_aic = float('inf')
        results = []
        for param in parameters_list:
            try:
                # order: 2, 2
                model = ARMA(train, order=(param[0], param[1])).fit()
            except ValueError:
                logger.info("参数错误：{}".format(param))
                continue
            except np.linalg.LinAlgError:
                logger.info("奇异矩阵错误")
                continue
            except Exception as ex:
                # print(ex)
                logger.debug(ex)
                logger.debug(traceback.format_exc())
                continue

            aic = model.aic
            if aic < best_aic:
                best_model = model
                best_aic = model.aic
                best_param = param
                # best_aic.replace([np.inf, -np.inf], 0)
            results.append([param, model.aic])
        results_table = pd.DataFrame(results)
        results_table.columns = ['parameters', 'aic']
        logger.info("最优模型 {}".format(best_model.summary()))
        pre_fore = np.round(best_model.forecast(1)[0], 0)[0]
        return pre_fore

    # 预测结果格式化写入ch中
    def save_format(self, kkid, date, hour, pre):
        pre_data = {'kkbh': kkid, 'day': date, 'hour': hour, 'predict_count': pre}
        logger.info('写入ch的数据格式:{}'.format(pre_data))
        try:
            save_data(pre_data)
        except Exception as ex:
            logger.error('预测值写入ch失败')
            logger.error(ex)
            logger.error(traceback.format_exc())

    # 主入口
    def main(self):
        bayonet_list = self.get_bayonet_info()
        if bayonet_list is not None:
            for kkid in bayonet_list:
                # 卡口信息，时刻，当前天
                if self.end_time == 'None':
                    day = datetime.now().strftime('%Y-%m-%d')
                else:
                    day = configger.end_time
                print(day)
                # 获取时间点序列
                hours = [str(h).zfill(2) for h in range(1, 24)]
                # 根据开始数据当前开始时间点为‘2021-01-24’
                start_time = self.start_time
                end_time = str(day)
                for hour in hours:
                    try:
                        print('查询数据的参数: {}  {}  {}, {}'.format(kkid, start_time, end_time, hour))
                        data = self.get_data(kkid, start_time=start_time, end_time=end_time, hour=hour)
                    except Exception as ex:
                        logger.error(' ch 读取数据失败')
                        logger.error(ex)
                        logger.info(traceback.format_exc())
                    if data is not None:
                        data['time'] = pd.to_datetime(data['time'])
                        data = data.set_index('time', drop=True)
                        # 对应的时间天
                        data_index = pd.date_range(start_time+' '+hour + ':00', end_time+' ' + hour + ':00', freq='D')
                        new_data = pd.DataFrame(index=data_index)
                        # 将数据时间序列不全，补全
                        new_data = pd.concat([new_data, data], axis=1)
                        print(new_data.shape)
                        if round(len(new_data.dropna())/len(new_data), 2) >= self.rate:
                            # 均值填补空值
                            new_data = new_data.fillna(round(new_data.mean(), 0))
                            # 对数据进行预测
                            pre_data = self.model_aram_pre(new_data)
                            self.save_format(kkid, day, hour, pre_data)
                        else:
                            logger.info('{}卡口, 结束时间{}， 时间点{}数据空值比率高于50%, 无法参与卡口过车数量预测'.format(kkid, end_time, hour))
                    else:
                        logger.info('{}卡口, 结束时间{}， 时间点{}无数据'.format(kkid, end_time, hour))

    # 测试读取数据
    def get_data(self, kkid, start_time, end_time, hour):
        data = fetch_data(kkid, start_time, end_time, hour)
        if not data.empty:
            return data
        else:
            # print("卡口{} 无数据".format(kkid))
            return None

    # 测试写入数据
    def test_save(self):
        data = {'kkid': "kkid", 'day': '2021-05-12', 'hour': '15', 'predict_count': 123}
        save_data(data)


arammodel = PreModel()
import time
if __name__ == '__main__':
    time0 = time.time()
    arammodel.main()
    time1 = time.time()
    logger.info('total time : {}'.format(time1-time0))